package net.pimpas.operation.persistence.serviceprovider;

import net.pimpas.core.configuration.exception.ConfigurationException;
import net.pimpas.core.serviceprovider.ServiceProviderAnnotation;
import net.pimpas.core.serviceprovider.instancetype.ServiceProviderInstanceType;
import net.pimpas.framework.PimpasFramework;
import net.pimpas.logging.serviceprovider.LoggingServiceProvider;
import net.pimpas.logging.serviceprovider.LoggingServiceProviderFactoryImpl;
import net.pimpas.operation.exception.OperationException;
import net.pimpas.operation.operation.OperationAnnotation;
import net.pimpas.operation.operation.OperationFactory;
import net.pimpas.operation.persistence.operation.OperationPersistence;
import net.pimpas.persistence.hibernate.serviceprovider.HibernateServiceProvider;
import net.pimpas.persistence.hibernate.serviceprovider.HibernateServiceProviderFactoryImpl;
import net.pimpas.util.reflection.ReflectionUtil;

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionException;
import org.hibernate.Transaction;
import org.hibernate.TransactionException;

@ServiceProviderAnnotation(
		instanceType=ServiceProviderInstanceType.SINGLETON,
		serviceProviderType=OperationPersistenceServiceProvider.class
)
public class OperationPersistenceServiceProviderImpl implements OperationPersistenceServiceProvider<Object> {

	private static final LoggingServiceProvider logger = LoggingServiceProviderFactoryImpl.getLoggingServiceProvider(OperationPersistenceServiceProviderImpl.class);
	
	public OperationPersistenceServiceProviderImpl() {
		
	}
	
	public Object call(OperationPersistence op, OperationPersistence parent, Object... parameters) throws OperationException {
		String name = null;
		Boolean sessionOpened = Boolean.FALSE;
		Session session = null;
		Transaction transaction = null;
		
		if(ReflectionUtil.checkAnnotationInClass(OperationAnnotation.class, op.getClass()))
			name = op.getClass().getAnnotation(OperationAnnotation.class).name();
		else
			name = op.getClass().getSimpleName();
		
		logger.debug("[PERSISTENCEOPERATION]: Initializing Operation {"+name+"}");
		try {
			op.setPersistenceServiceProvider(this.getPersistenceServiceProvider());
			logger.debug("[PERSISTENCEOPERATION]: Getting Session for Operation {"+name+"}");
			// check parent
			if(parent != null) {
				logger.debug("[PERSISTENCEOPERATION]: The Operation {"+name+"} have a parent, using the same session and Transaction");
				session = parent.getSession();
			} else {
				// try with framework context
				Session s = this.getPersistenceServiceProvider().getSession(PimpasFramework.getCurrentInstance().getContext());
				if(s != null) {
					logger.debug("[PERSISTENCEOPERATION]: The Operation {"+name+"} using the Session found in FrameworkContext.");
					session = s;
				} else 
					try {
						session = this.getPersistenceServiceProvider().getSessionFactory().getCurrentSession();
					} catch(Throwable e) {
						logger.debug("[PERSISTENCEOPERATION]: No currentSession found, creating a new One for Operation {"+name+"}");
						session = this.getPersistenceServiceProvider().getSessionFactory().openSession();
						sessionOpened = Boolean.TRUE;
					}
			}
			
			op.setSession(session);
			op.init(parameters);
		} catch (OperationException e) {
			logger.fatal(e);
			throw new OperationException(e);
		} catch (SessionException e) {
			logger.fatal(e);
			throw new OperationException(e);
		} catch (HibernateException e) {
			logger.fatal(e);
			throw new OperationException(e);
		}
		
		logger.debug("[PERSISTENCEOPERATION]: Creating Transaction for Operation {"+name+"}");
		Boolean transactionAlreadyBegun = Boolean.FALSE;
		try {
			if(parent != null)
				transaction = parent.getSession().getTransaction();
			else {
				transactionAlreadyBegun = session.getTransaction().isActive();
				transaction = session.beginTransaction();
			}
		} catch (SessionException e3) {
			logger.fatal(e3);
			try {
				if(parent == null)
					session.close();
			} catch (SessionException e) {
			}
			
			throw new OperationException(e3);
		}
		
		Object result = null;
		try {
			logger.debug("[PERSISTENCEOPERATION]: Starting Operation {"+name+"}");
			op.start();
			logger.debug("[PERSISTENCEOPERATION]: Calling Operation {"+name+"}");
			result = op.call();
			// commiting
			logger.debug("[PERSISTENCEOPERATION]: Commiting Operation {"+name+"}");
			op.commit();
			if(parent == null && transactionAlreadyBegun.equals(Boolean.FALSE))
				transaction.commit();
		} catch (Exception e) {
			logger.debug("[PERSISTENCEOPERATION]: Error in Operation {"+name+"}", e);
			logger.debug("[PERSISTENCEOPERATION]: RollingBack Operation: {"+name+"}");
			try {
				op.rollBack();
			} catch (OperationException e1) {
				logger.fatal(e1);
			} finally {
				try {
					if(parent == null && transactionAlreadyBegun.equals(Boolean.FALSE))
						transaction.rollback();
				} catch (TransactionException e1) {
					logger.fatal(e1);
				}
			}
			throw new OperationException(e);
		} finally {
			logger.debug("[PERSISTENCEOPERATION]: Finalizing Operation {"+name+"}");
			try {
				op.end();
			} catch (OperationException e) {
				logger.fatal(e);
				throw new OperationException(e);
			} finally {
				if(sessionOpened == Boolean.TRUE) {
					// close session
					try {
						session.close();
					} catch(Throwable e) {
						logger.error("[PERSISTENCEOPERATION]: Error when trying to close Session for Operation {"+name+"}", e);
					}
				}
			}
		}

		logger.debug("[PERSISTENCEOPERATION]: Returning "+result+" for Operation {"+name+"}");
		return result;
	}
	
	public Object call(OperationPersistence op, Object... parameters) throws OperationException {
		return this.call(op, null, parameters);
	}

	public void config() throws ConfigurationException {
		
	}
	
	@SuppressWarnings("static-access")
	public Object call(String name, Object... parameters) throws OperationException {
		OperationPersistence op = (OperationPersistence<?>) OperationFactory.getInstance().find(name);
		if(op == null)
			throw new OperationException("CAN'T FIND OPERATION WITH NAME '"+name+"'.");
		return this.call(op, null, parameters);
	}

	public Object call(String name, Class operationType, Object... parameters) throws OperationException {
		return this.call(name, null, operationType, parameters);
	}

	@SuppressWarnings("static-access")
	public Object call(String name, OperationPersistence parent, Object... parameters) throws OperationException {
		OperationPersistence op = (OperationPersistence<?>) OperationFactory.getInstance().find(name);
		if(op == null)
			throw new OperationException("CAN'T FIND OPERATION WITH NAME '"+name+"'.");
		return this.call(op, parent, parameters);
	}

	@SuppressWarnings("static-access")
	public Object call(String name, OperationPersistence parent, Class operationType, Object... parameters) throws OperationException {
		OperationPersistence op = (OperationPersistence<?>) OperationFactory.getInstance().find(operationType, name);
		if(op == null)
			throw new OperationException("CAN'T FIND OPERATION WITH NAME '"+name+"'.");
		return this.call(op, parent, parameters);
	}

	public boolean isTerminated() {
		return false;
	}

	public HibernateServiceProvider getPersistenceServiceProvider() {
		return HibernateServiceProviderFactoryImpl.getPersistenceServiceProvider();
	}
}